๐๏ธGitะฏัะฐ๐๏ธ
Commit 11bc37c96878cd1d9a822c1dad658f43c9be6604
Parents : 92cfbae
Author : James Rich <2199651+jamesarich@users.noreply.github.com>
Signature : Signature validation error
Date : 2026-05-19T12:06:44-07:00
Committer : GitHub <noreply@github.com>
Date : 2026-05-19T14:06:44-05:00
docs: move English sources into docs/en/ locale folder (#5501)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Changes
72 files changed, 3650 insertions(+), 3605 deletions(-)
Diff
diff --git a/.github/workflows/docs-governance.yml b/.github/workflows/docs-governance.yml
index 4b526c996f..a10ed89b85 100644
--- a/.github/workflows/docs-governance.yml
+++ b/.github/workflows/docs-governance.yml
@@ -39,7 +39,7 @@ jobs:
'^feature/.*/src/commonMain/.*/(ui|component|screen)/|^feature/.*/src/androidMain/.*/ui/|^core/ui/src/commonMain/' \
| grep -v 'Test\|Preview\|__Snapshots__' || true)
- docs_changed=$(echo "$changed" | grep -E '^docs/(user|developer)/' || true)
+ docs_changed=$(echo "$changed" | grep -E '^docs/en/(user|developer)/' || true)
echo "views_changed<<EOF" >> "$GITHUB_OUTPUT"
echo "$views_changed" >> "$GITHUB_OUTPUT"
@@ -64,9 +64,9 @@ jobs:
const body = [
'## ๐ Docs staleness check โ advisory',
'',
- 'This PR modifies user-facing UI source files but does not update any page under `docs/user/` or `docs/developer/`.',
+ 'This PR modifies user-facing UI source files but does not update any page under `docs/en/user/` or `docs/en/developer/`.',
'',
- '> โ ๏ธ Doc changes propagate to **3 consumers**: in-app docs browser, Jekyll site (GitHub Pages), and meshtastic.org (Docusaurus sync). Updating a page in `docs/` automatically flows to all three.',
+ '> โ ๏ธ Doc changes propagate to **3 consumers**: in-app docs browser, Jekyll site (GitHub Pages), and meshtastic.org (Docusaurus sync). Updating a page in `docs/en/` automatically flows to all three.',
'',
'**Changed source files:**',
'```',
@@ -76,19 +76,19 @@ jobs:
'**What to check:**',
'| Changed area | Likely doc page |',
'|---|---|',
- '| `feature/messaging/` | `docs/user/messages-and-channels.md` |',
- '| `feature/node/` | `docs/user/nodes.md` or `docs/user/node-metrics.md` |',
- '| `feature/map/` | `docs/user/map-and-waypoints.md` |',
- '| `feature/connections/` | `docs/user/connections.md` |',
- '| `feature/settings/` | `docs/user/settings-radio-user.md` or `docs/user/settings-module-admin.md` |',
- '| `feature/firmware/` | `docs/user/firmware.md` |',
- '| `feature/intro/` | `docs/user/onboarding.md` |',
- '| `feature/discovery/` | `docs/user/discovery.md` |',
+ '| `feature/messaging/` | `docs/en/user/messages-and-channels.md` |',
+ '| `feature/node/` | `docs/en/user/nodes.md` or `docs/en/user/node-metrics.md` |',
+ '| `feature/map/` | `docs/en/user/map-and-waypoints.md` |',
+ '| `feature/connections/` | `docs/en/user/connections.md` |',
+ '| `feature/settings/` | `docs/en/user/settings-radio-user.md` or `docs/en/user/settings-module-admin.md` |',
+ '| `feature/firmware/` | `docs/en/user/firmware.md` |',
+ '| `feature/intro/` | `docs/en/user/onboarding.md` |',
+ '| `feature/discovery/` | `docs/en/user/discovery.md` |',
'| `feature/docs/` | Internal docs infrastructure |',
- '| `core/ui/` | `docs/developer/codebase.md` or component-specific user pages |',
+ '| `core/ui/` | `docs/en/developer/codebase.md` or component-specific user pages |',
'',
'**New page checklist** (if adding a new doc page):',
- '1. Create the `.md` file in `docs/user/` or `docs/developer/` with `last_updated` frontmatter',
+ '1. Create the `.md` file in `docs/en/user/` or `docs/en/developer/` with `last_updated` frontmatter',
'2. Register in `DocBundleLoader.kt` with string resources (in-app browser)',
'3. Jekyll and Docusaurus sync pick up new pages automatically โ no config change needed',
'',
@@ -141,14 +141,14 @@ jobs:
owner: context.repo.owner,
repo: context.repo.repo,
comment_id: existing.id,
- body: '## โ
Docs staleness check passed\n\nThis PR includes updates to `docs/` alongside the source changes. Thank you!',
+ body: '## โ
Docs staleness check passed\n\nThis PR includes updates to `docs/en/` alongside the source changes. Thank you!',
});
}
- name: Advisory status
if: steps.changed.outputs.stale == 'true'
run: |
- echo "::warning::UI source files changed without corresponding docs/ updates."
+ echo "::warning::UI source files changed without corresponding docs/en/ updates."
echo "Add the 'skip-docs-check' label if this PR does not require a doc update."
echo "NOTE: This check is advisory while docs coverage matures across platforms."
echo "To upgrade to blocking, change this step to 'exit 1'."
@@ -170,7 +170,7 @@ jobs:
node-version: "24"
- name: Validate internal links
- run: node scripts/validate-doc-links.js docs
+ run: node scripts/validate-doc-links.js docs/en
- name: Check doc coverage
run: node scripts/check-doc-coverage.js .
@@ -179,7 +179,7 @@ jobs:
run: |
loader="feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt"
missing=0
- for f in docs/user/*.md docs/developer/*.md; do
+ for f in docs/en/user/*.md docs/en/developer/*.md; do
slug=$(basename "$f" .md)
if ! grep -q "\"$slug\"" "$loader"; then
echo "ERROR: $slug not registered in DocBundleLoader.kt"
diff --git a/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt
index 15cbdca9ee..4e9745934a 100644
--- a/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt
+++ b/build-logic/convention/src/main/kotlin/org/meshtastic/buildlogic/DocsTasks.kt
@@ -100,9 +100,10 @@ abstract class GenerateDocsBundleTask : DefaultTask() {
val indexEntries = mutableListOf<String>()
var pageCount = 0
- // Process English user and developer directories
+ // Process English user and developer directories (under docs/en/)
+ val enDir = File(src, "en")
listOf("user", "developer").forEach { section ->
- val sectionDir = File(src, section)
+ val sectionDir = File(enDir, section)
if (!sectionDir.exists()) return@forEach
sectionDir.listFiles { f -> f.extension == "md" }?.sortedBy { it.name }?.forEach { mdFile ->
@@ -143,8 +144,9 @@ abstract class GenerateDocsBundleTask : DefaultTask() {
// Process Crowdin locale directories: docs/{qualifier}/user/*.md
// Crowdin %android_code% produces: fr, pt-rBR, zh-rCN, zh-rTW
+ // Skip "en" since English sources are handled above.
val localePattern = Regex("^[a-z]{2,3}(-r[A-Z]{2})?$")
- src.listFiles { f -> f.isDirectory && localePattern.matches(f.name) }
+ src.listFiles { f -> f.isDirectory && localePattern.matches(f.name) && f.name != "en" }
?.sortedBy { it.name }
?.forEach { localeDir ->
val locale = localeDir.name
@@ -198,7 +200,7 @@ abstract class GenerateDocsBundleTask : DefaultTask() {
File(cssDir, "docs.css").writeText(generateCss())
// Write locales manifest (for consumers that need to know available translations)
- val localesManifest = src.listFiles { f -> f.isDirectory && localePattern.matches(f.name) }
+ val localesManifest = src.listFiles { f -> f.isDirectory && localePattern.matches(f.name) && f.name != "en" }
?.map { it.name }?.sorted() ?: emptyList()
val manifestFile = File(out, "locales.json")
manifestFile.writeText(localesManifest.joinToString(", ", "[", "]") { "\"$it\"" })
@@ -258,7 +260,7 @@ abstract class GenerateDocsBundleTask : DefaultTask() {
.replace(Regex("^---[\\s\\S]*?---\\s*", RegexOption.MULTILINE), "")
.replace("&", "&").replace("<", "<").replace(">", ">")
val dir = if (locale == "ar") "rtl" else "ltr"
- // Locale pages are one level deeper: docs/{locale}/user/foo.html vs docs/user/foo.html
+ // Locale pages are one level deeper: docs/{locale}/user/foo.html vs docs/en/user/foo.html
val cssPath = if (locale != "en") "../../styles/docs.css" else "../styles/docs.css"
return """
|<!DOCTYPE html>
diff --git a/crowdin.yml b/crowdin.yml
index 64a130ca70..6fa9688fea 100644
--- a/crowdin.yml
+++ b/crowdin.yml
@@ -11,10 +11,11 @@ files:
- source: /fastlane/metadata/android/en-US/changelogs/default.txt
translation: /fastlane/metadata/android/%locale%/changelogs/%original_file_name%
# In-app docs โ user guide only (developer guide is English-only)
+ # English sources live under docs/en/; translations land at docs/{android_code}/
# Uses %android_code% to output Android/CMP qualifier format directly (pt-rBR, zh-rCN, fr)
- - source: /docs/user/*.md
+ - source: /docs/en/user/*.md
translation: /docs/%android_code%/user/%original_file_name%
type: md
- - source: /docs/index.md
+ - source: /docs/en/index.md
translation: /docs/%android_code%/%original_file_name%
type: md
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 0000000000..35461fd8d8
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,45 @@
+# Documentation Structure
+
+This directory contains the source documentation for the Meshtastic Android/Desktop/iOS app.
+It serves three consumers:
+
+1. **In-app docs browser** โ bundled via Compose Resources at build time
+2. **Jekyll site** โ GitHub Pages (this directory is the Jekyll source root)
+3. **meshtastic.org** โ Docusaurus sync (upstream consumption)
+
+## Locale Layout
+
+```
+docs/
+โโโ _config.yml, _data/, _layouts/, _sass/ โ Jekyll site infrastructure
+โโโ en/ โ English source (edit here)
+โ โโโ user/ โ User Guide pages
+โ โโโ developer/ โ Developer Guide pages
+โ โโโ index.md โ Site home page
+โ โโโ user.md โ User Guide nav parent
+โ โโโ developer.md โ Developer Guide nav parent
+โ โโโ translations.md โ Translations landing page
+โโโ fr-rFR/ โ French (Crowdin-generated)
+โ โโโ user/ โ Translated user guide
+โโโ de-rDE/ โ German (Crowdin-generated)
+โ โโโ user/
+โโโ ... โ Other locales
+```
+
+## Editing Guidelines
+
+- **English source**: Edit files under `docs/en/`. These are the authoritative source.
+- **Translations**: Do **not** edit files in locale folders directly. They are auto-generated
+ by [Crowdin](https://crowdin.com/project/meshtastic-android) and will be overwritten on sync.
+ Contribute translations via Crowdin instead.
+- **Adding a page**: Create the `.md` file in `docs/en/user/` or `docs/en/developer/`, then
+ register it in `feature/docs/.../DocBundleLoader.kt` for in-app bundling.
+
+## How Translations Work
+
+1. English source files (`docs/en/user/*.md`) are uploaded to Crowdin as translation sources
+2. Volunteers translate via the Crowdin web UI
+3. Crowdin PRs land translated files at `docs/{android_code}/user/*.md` (e.g., `fr-rFR`, `pt-rBR`)
+4. At build time, the Gradle `syncTranslatedDocsToComposeResources` task bundles them into
+ locale-qualified Compose Resources for the in-app reader
+5. The in-app `DocBundleLoader` tries the user's locale first, then falls back to English
diff --git a/docs/_config.yml b/docs/_config.yml
index bd7c6dc009..d29e8509e4 100644
--- a/docs/_config.yml
+++ b/docs/_config.yml
@@ -28,12 +28,12 @@ color_scheme: meshtastic
# Default front-matter for pages in subdirectories
defaults:
- scope:
- path: "user/"
+ path: "en/user/"
values:
parent: User Guide
layout: default
- scope:
- path: "developer/"
+ path: "en/developer/"
values:
parent: Developer Guide
layout: default
@@ -41,229 +41,229 @@ defaults:
# They use a dedicated locale layout with a back-link to the English version.
# Auto-generated from Android app locales (values-* resource dirs).
- scope:
- path: "ar/"
+ path: "ar"
values:
layout: locale_page
locale: ar
nav_exclude: true
- scope:
- path: "be/"
+ path: "be"
values:
layout: locale_page
locale: be
nav_exclude: true
- scope:
- path: "bg/"
+ path: "bg"
values:
layout: locale_page
locale: bg
nav_exclude: true
- scope:
- path: "ca/"
+ path: "ca"
values:
layout: locale_page
locale: ca
nav_exclude: true
- scope:
- path: "cs/"
+ path: "cs"
values:
layout: locale_page
locale: cs
nav_exclude: true
- scope:
- path: "de/"
+ path: "de"
values:
layout: locale_page
locale: de
nav_exclude: true
- scope:
- path: "el/"
+ path: "el"
values:
layout: locale_page
locale: el
nav_exclude: true
- scope:
- path: "es/"
+ path: "es"
values:
layout: locale_page
locale: es
nav_exclude: true
- scope:
- path: "et/"
+ path: "et"
values:
layout: locale_page
locale: et
nav_exclude: true
- scope:
- path: "fi/"
+ path: "fi"
values:
layout: locale_page
locale: fi
nav_exclude: true
- scope:
- path: "fr/"
+ path: "fr"
values:
layout: locale_page
locale: fr
nav_exclude: true
- scope:
- path: "ga/"
+ path: "ga"
values:
layout: locale_page
locale: ga
nav_exclude: true
- scope:
- path: "gl/"
+ path: "gl"
values:
layout: locale_page
locale: gl
nav_exclude: true
- scope:
- path: "he/"
+ path: "he"
values:
layout: locale_page
locale: he
nav_exclude: true
- scope:
- path: "hr/"
+ path: "hr"
values:
layout: locale_page
locale: hr
nav_exclude: true
- scope:
- path: "ht/"
+ path: "ht"
values:
layout: locale_page
locale: ht
nav_exclude: true
- scope:
- path: "hu/"
+ path: "hu"
values:
layout: locale_page
locale: hu
nav_exclude: true
- scope:
- path: "is/"
+ path: "is"
values:
layout: locale_page
locale: is
nav_exclude: true
- scope:
- path: "it/"
+ path: "it"
values:
layout: locale_page
locale: it
nav_exclude: true
- scope:
- path: "ja/"
+ path: "ja"
values:
layout: locale_page
locale: ja
nav_exclude: true
- scope:
- path: "ko/"
+ path: "ko"
values:
layout: locale_page
locale: ko
nav_exclude: true
- scope:
- path: "lt/"
+ path: "lt"
values:
layout: locale_page
locale: lt
nav_exclude: true
- scope:
- path: "nl/"
+ path: "nl"
values:
layout: locale_page
locale: nl
nav_exclude: true
- scope:
- path: "no/"
+ path: "no"
values:
layout: locale_page
locale: no
nav_exclude: true
- scope:
- path: "pl/"
+ path: "pl"
values:
layout: locale_page
locale: pl
nav_exclude: true
- scope:
- path: "pt/"
+ path: "pt"
values:
layout: locale_page
locale: pt
nav_exclude: true
- scope:
- path: "pt-rBR/"
+ path: "pt-rBR"
values:
layout: locale_page
locale: pt-rBR
nav_exclude: true
- scope:
- path: "ro/"
+ path: "ro"
values:
layout: locale_page
locale: ro
nav_exclude: true
- scope:
- path: "ru/"
+ path: "ru"
values:
layout: locale_page
locale: ru
nav_exclude: true
- scope:
- path: "sk/"
+ path: "sk"
values:
layout: locale_page
locale: sk
nav_exclude: true
- scope:
- path: "sl/"
+ path: "sl"
values:
layout: locale_page
locale: sl
nav_exclude: true
- scope:
- path: "sq/"
+ path: "sq"
values:
layout: locale_page
locale: sq
nav_exclude: true
- scope:
- path: "sr/"
+ path: "sr"
values:
layout: locale_page
locale: sr
nav_exclude: true
- scope:
- path: "sv/"
+ path: "sv"
values:
layout: locale_page
locale: sv
nav_exclude: true
- scope:
- path: "tr/"
+ path: "tr"
values:
layout: locale_page
locale: tr
nav_exclude: true
- scope:
- path: "uk/"
+ path: "uk"
values:
layout: locale_page
locale: uk
nav_exclude: true
- scope:
- path: "zh-rCN/"
+ path: "zh-rCN"
values:
layout: locale_page
locale: zh-rCN
nav_exclude: true
- scope:
- path: "zh-rTW/"
+ path: "zh-rTW"
values:
layout: locale_page
locale: zh-rTW
@@ -285,5 +285,6 @@ exclude:
- Gemfile
- Gemfile.lock
- assets/screenshots/.gitkeep
+ - README.md
- "*.sh"
diff --git a/docs/_layouts/locale_page.html b/docs/_layouts/locale_page.html
index 167e80b09a..b8e958245e 100644
--- a/docs/_layouts/locale_page.html
+++ b/docs/_layouts/locale_page.html
@@ -7,7 +7,8 @@ layout: default
{% assign page_path = page.path %}
{% assign path_parts = page_path | split: "/" %}
{% assign remaining_parts = path_parts | slice: 1, path_parts.size %}
-{% assign en_path = remaining_parts | join: "/" | replace: ".md", "" %}
+{% assign en_relative = remaining_parts | join: "/" | replace: ".md", "" %}
+{% assign en_path = "en/" | append: en_relative %}
<div class="locale-page-banner" {% if locale_info.dir == "rtl" %}dir="rtl"{% endif %}>
<p class="locale-notice">
diff --git a/docs/developer.md b/docs/en/developer.md
similarity index 100%
rename from docs/developer.md
rename to docs/en/developer.md
diff --git a/docs/developer/adding-a-feature-module.md b/docs/en/developer/adding-a-feature-module.md
similarity index 100%
rename from docs/developer/adding-a-feature-module.md
rename to docs/en/developer/adding-a-feature-module.md
diff --git a/docs/developer/architecture.md b/docs/en/developer/architecture.md
similarity index 100%
rename from docs/developer/architecture.md
rename to docs/en/developer/architecture.md
diff --git a/docs/developer/codebase.md b/docs/en/developer/codebase.md
similarity index 100%
rename from docs/developer/codebase.md
rename to docs/en/developer/codebase.md
diff --git a/docs/developer/contributing.md b/docs/en/developer/contributing.md
similarity index 100%
rename from docs/developer/contributing.md
rename to docs/en/developer/contributing.md
diff --git a/docs/developer/measurement.md b/docs/en/developer/measurement.md
similarity index 100%
rename from docs/developer/measurement.md
rename to docs/en/developer/measurement.md
diff --git a/docs/developer/navigation-and-deep-links.md b/docs/en/developer/navigation-and-deep-links.md
similarity index 100%
rename from docs/developer/navigation-and-deep-links.md
rename to docs/en/developer/navigation-and-deep-links.md
diff --git a/docs/developer/persistence.md b/docs/en/developer/persistence.md
similarity index 100%
rename from docs/developer/persistence.md
rename to docs/en/developer/persistence.md
diff --git a/docs/developer/testing.md b/docs/en/developer/testing.md
similarity index 100%
rename from docs/developer/testing.md
rename to docs/en/developer/testing.md
diff --git a/docs/developer/transport.md b/docs/en/developer/transport.md
similarity index 100%
rename from docs/developer/transport.md
rename to docs/en/developer/transport.md
diff --git a/docs/index.md b/docs/en/index.md
similarity index 100%
rename from docs/index.md
rename to docs/en/index.md
diff --git a/docs/translations.md b/docs/en/translations.md
similarity index 100%
rename from docs/translations.md
rename to docs/en/translations.md
diff --git a/docs/user.md b/docs/en/user.md
similarity index 100%
rename from docs/user.md
rename to docs/en/user.md
diff --git a/docs/user/connections.md b/docs/en/user/connections.md
similarity index 100%
rename from docs/user/connections.md
rename to docs/en/user/connections.md
diff --git a/docs/user/desktop.md b/docs/en/user/desktop.md
similarity index 100%
rename from docs/user/desktop.md
rename to docs/en/user/desktop.md
diff --git a/docs/user/discovery.md b/docs/en/user/discovery.md
similarity index 100%
rename from docs/user/discovery.md
rename to docs/en/user/discovery.md
diff --git a/docs/user/firmware.md b/docs/en/user/firmware.md
similarity index 100%
rename from docs/user/firmware.md
rename to docs/en/user/firmware.md
diff --git a/docs/user/map-and-waypoints.md b/docs/en/user/map-and-waypoints.md
similarity index 100%
rename from docs/user/map-and-waypoints.md
rename to docs/en/user/map-and-waypoints.md
diff --git a/docs/user/messages-and-channels.md b/docs/en/user/messages-and-channels.md
similarity index 100%
rename from docs/user/messages-and-channels.md
rename to docs/en/user/messages-and-channels.md
diff --git a/docs/user/mqtt.md b/docs/en/user/mqtt.md
similarity index 100%
rename from docs/user/mqtt.md
rename to docs/en/user/mqtt.md
diff --git a/docs/user/node-metrics.md b/docs/en/user/node-metrics.md
similarity index 100%
rename from docs/user/node-metrics.md
rename to docs/en/user/node-metrics.md
diff --git a/docs/user/nodes.md b/docs/en/user/nodes.md
similarity index 100%
rename from docs/user/nodes.md
rename to docs/en/user/nodes.md
diff --git a/docs/user/onboarding.md b/docs/en/user/onboarding.md
similarity index 100%
rename from docs/user/onboarding.md
rename to docs/en/user/onboarding.md
diff --git a/docs/user/settings-module-admin.md b/docs/en/user/settings-module-admin.md
similarity index 100%
rename from docs/user/settings-module-admin.md
rename to docs/en/user/settings-module-admin.md
diff --git a/docs/user/settings-radio-user.md b/docs/en/user/settings-radio-user.md
similarity index 100%
rename from docs/user/settings-radio-user.md
rename to docs/en/user/settings-radio-user.md
diff --git a/docs/user/signal-meter.md b/docs/en/user/signal-meter.md
similarity index 100%
rename from docs/user/signal-meter.md
rename to docs/en/user/signal-meter.md
diff --git a/docs/user/tak.md b/docs/en/user/tak.md
similarity index 100%
rename from docs/user/tak.md
rename to docs/en/user/tak.md
diff --git a/docs/user/telemetry-and-sensors.md b/docs/en/user/telemetry-and-sensors.md
similarity index 100%
rename from docs/user/telemetry-and-sensors.md
rename to docs/en/user/telemetry-and-sensors.md
diff --git a/docs/user/translate.md b/docs/en/user/translate.md
similarity index 100%
rename from docs/user/translate.md
rename to docs/en/user/translate.md
diff --git a/docs/user/units-and-locale.md b/docs/en/user/units-and-locale.md
similarity index 100%
rename from docs/user/units-and-locale.md
rename to docs/en/user/units-and-locale.md
diff --git a/feature/docs/build.gradle.kts b/feature/docs/build.gradle.kts
index 1f1a72b72a..a783f88ca8 100644
--- a/feature/docs/build.gradle.kts
+++ b/feature/docs/build.gradle.kts
@@ -59,24 +59,16 @@ kotlin {
*/
val syncDocsToComposeResources by
tasks.registering(Sync::class) {
- description = "Syncs docs/ markdown source into composeResources for in-app bundling"
+ description = "Syncs docs/en/ markdown source into composeResources for in-app bundling"
group = "docs"
- val docsSourceDir = rootProject.layout.projectDirectory.dir("docs")
+ val docsEnDir = rootProject.layout.projectDirectory.dir("docs/en")
val screenshotsDir = rootProject.layout.projectDirectory.dir("docs/screenshots")
val composeResourcesTarget = layout.projectDirectory.dir("src/commonMain/composeResources/files/docs")
- from(docsSourceDir) {
+ from(docsEnDir) {
include("user/**/*.md")
include("developer/**/*.md")
- // Exclude Jekyll/site-only files that are not needed in-app
- exclude("_config.yml")
- exclude("_data/**")
- exclude("_includes/**")
- exclude("_layouts/**")
- exclude("index.md")
- exclude("assets/**")
- exclude("Gemfile*")
}
// FR-038: Bundle screenshots into assets/screenshots/ to match markdown image paths.
@@ -117,8 +109,7 @@ val syncTranslatedDocsToComposeResources by
from(docsDir) {
// Crowdin outputs dirs in Android qualifier format (fr, pt-rBR, zh-rCN)
include("*/user/**/*.md")
- exclude("user/**")
- exclude("developer/**")
+ exclude("en/**")
exclude("_*/**")
exclude("assets/**")
exclude("screenshots/**")
@@ -133,6 +124,8 @@ val syncTranslatedDocsToComposeResources by
if (segments.size >= 3) {
val qualifier = segments[0]
val rest = segments.drop(1).joinToString("/")
+ // Output: files/{locale}/docs/user/page.md
+ // English source lives at: docs/en/$rest
path = "$qualifier/docs/$rest"
}
}
diff --git a/feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt b/feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt
index 20af93b8c2..9ea02dbf48 100644
--- a/feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt
+++ b/feature/docs/src/commonMain/kotlin/org/meshtastic/feature/docs/data/DocBundleLoader.kt
@@ -241,7 +241,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"onboarding",
CoreRes.string.doc_title_onboarding,
CoreRes.string.doc_keywords_onboarding,
- "docs/user/onboarding.html",
+ "en/user/onboarding.html",
1,
listOf("first-launch", "setup", "intro"),
3200,
@@ -251,7 +251,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"connections",
CoreRes.string.doc_title_connections,
CoreRes.string.doc_keywords_connections,
- "docs/user/connections.html",
+ "en/user/connections.html",
2,
listOf("bluetooth", "usb", "tcp", "pairing"),
4100,
@@ -261,7 +261,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"messages-and-channels",
CoreRes.string.doc_title_messages,
CoreRes.string.doc_keywords_messages,
- "docs/user/messages-and-channels.html",
+ "en/user/messages-and-channels.html",
3,
listOf("channels", "direct-messages", "messaging", "conversations"),
4500,
@@ -271,7 +271,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"nodes",
CoreRes.string.doc_title_nodes,
CoreRes.string.doc_keywords_nodes,
- "docs/user/nodes.html",
+ "en/user/nodes.html",
4,
listOf("node-list", "mesh-nodes", "peers"),
3800,
@@ -281,7 +281,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"node-metrics",
CoreRes.string.doc_title_node_metrics,
CoreRes.string.doc_keywords_node_metrics,
- "docs/user/node-metrics.html",
+ "en/user/node-metrics.html",
5,
listOf("metrics", "telemetry", "device-metrics", "signal"),
5200,
@@ -291,7 +291,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"map-and-waypoints",
CoreRes.string.doc_title_map,
CoreRes.string.doc_keywords_map,
- "docs/user/map-and-waypoints.html",
+ "en/user/map-and-waypoints.html",
6,
listOf("map", "waypoints", "gps", "location"),
3600,
@@ -301,7 +301,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"settings-radio-user",
CoreRes.string.doc_title_settings_radio,
CoreRes.string.doc_keywords_settings_radio,
- "docs/user/settings-radio-user.html",
+ "en/user/settings-radio-user.html",
7,
listOf("settings", "radio-config", "user-config", "lora"),
6800,
@@ -311,7 +311,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"settings-module-admin",
CoreRes.string.doc_title_settings_module,
CoreRes.string.doc_keywords_settings_module,
- "docs/user/settings-module-admin.html",
+ "en/user/settings-module-admin.html",
8,
listOf("modules", "module-config", "administration"),
5500,
@@ -321,7 +321,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"telemetry-and-sensors",
CoreRes.string.doc_title_telemetry,
CoreRes.string.doc_keywords_telemetry,
- "docs/user/telemetry-and-sensors.html",
+ "en/user/telemetry-and-sensors.html",
9,
listOf("sensors", "environment", "weather", "power-metrics"),
4800,
@@ -331,7 +331,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"tak",
CoreRes.string.doc_title_tak,
CoreRes.string.doc_keywords_tak,
- "docs/user/tak.html",
+ "en/user/tak.html",
10,
listOf("tak", "atak", "team-awareness-kit"),
2400,
@@ -341,7 +341,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"mqtt",
CoreRes.string.doc_title_mqtt,
CoreRes.string.doc_keywords_mqtt,
- "docs/user/mqtt.html",
+ "en/user/mqtt.html",
11,
listOf("mqtt", "internet-bridge", "broker"),
4200,
@@ -351,7 +351,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"discovery",
CoreRes.string.doc_title_discovery,
CoreRes.string.doc_keywords_discovery,
- "docs/user/discovery.html",
+ "en/user/discovery.html",
12,
listOf("mesh-discovery", "local-discovery", "network-scan"),
2800,
@@ -361,7 +361,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"firmware",
CoreRes.string.doc_title_firmware,
CoreRes.string.doc_keywords_firmware,
- "docs/user/firmware.html",
+ "en/user/firmware.html",
13,
listOf("firmware", "update", "ota", "flash"),
3400,
@@ -371,7 +371,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"desktop",
CoreRes.string.doc_title_desktop,
CoreRes.string.doc_keywords_desktop,
- "docs/user/desktop.html",
+ "en/user/desktop.html",
14,
listOf("desktop", "linux", "macos", "windows", "jvm"),
3900,
@@ -381,7 +381,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"signal-meter",
CoreRes.string.doc_title_signal_meter,
CoreRes.string.doc_keywords_signal_meter,
- "docs/user/signal-meter.html",
+ "en/user/signal-meter.html",
15,
listOf("signal-quality", "signal-strength", "rssi", "snr"),
3500,
@@ -391,7 +391,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"units-and-locale",
CoreRes.string.doc_title_units,
CoreRes.string.doc_keywords_units,
- "docs/user/units-and-locale.html",
+ "en/user/units-and-locale.html",
16,
listOf("measurement", "units", "locale", "metric", "imperial"),
3800,
@@ -401,7 +401,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"translate",
CoreRes.string.doc_title_translate,
CoreRes.string.doc_keywords_translate,
- "docs/user/translate.html",
+ "en/user/translate.html",
17,
listOf("crowdin", "localization", "language", "i18n", "contribute"),
3700,
@@ -433,7 +433,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"architecture",
"Architecture",
"developer",
- "docs/developer/architecture.html",
+ "en/developer/architecture.html",
1,
listOf("architecture", "kmp", "module", "layer", "core", "feature", "compose"),
listOf("layers", "module-architecture", "kmp"),
@@ -444,7 +444,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"codebase",
"Codebase",
"developer",
- "docs/developer/codebase.html",
+ "en/developer/codebase.html",
2,
listOf("codebase", "repository", "layout", "gradle", "build", "namespace", "convention"),
listOf("repository-layout", "project-structure", "source-code"),
@@ -455,7 +455,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"adding-a-feature-module",
"Adding a Feature Module",
"developer",
- "docs/developer/adding-a-feature-module.html",
+ "en/developer/adding-a-feature-module.html",
3,
listOf("module", "feature", "new", "create", "plugin", "di", "koin"),
listOf("new-module", "feature-module", "module-guide"),
@@ -466,7 +466,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"navigation-and-deep-links",
"Navigation & Deep Links",
"developer",
- "docs/developer/navigation-and-deep-links.html",
+ "en/developer/navigation-and-deep-links.html",
4,
listOf("navigation", "deeplink", "route", "navkey", "backstack", "typed"),
listOf("deeplinks", "navigation-3", "routes"),
@@ -477,7 +477,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"transport",
"Transport",
"developer",
- "docs/developer/transport.html",
+ "en/developer/transport.html",
5,
listOf("transport", "ble", "serial", "tcp", "radio", "connection"),
listOf("ble", "serial", "tcp", "radio-transport"),
@@ -488,7 +488,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"persistence",
"Persistence",
"developer",
- "docs/developer/persistence.html",
+ "en/developer/persistence.html",
6,
listOf("room", "database", "datastore", "prefs", "storage", "migration"),
listOf("room", "database", "datastore", "prefs"),
@@ -499,7 +499,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"testing",
"Testing",
"developer",
- "docs/developer/testing.html",
+ "en/developer/testing.html",
7,
listOf("test", "unit", "screenshot", "compose", "roborazzi", "ci"),
listOf("tests", "unit-tests", "screenshot-tests"),
@@ -510,7 +510,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"contributing",
"Contributing",
"developer",
- "docs/developer/contributing.html",
+ "en/developer/contributing.html",
8,
listOf("contributing", "pull-request", "branch", "commit", "style", "pr"),
listOf("contributing", "pull-request", "branch-naming"),
@@ -521,7 +521,7 @@ class DefaultDocBundleLoader : DocBundleLoader {
"measurement",
"Measurement & Formatting",
"developer",
- "docs/developer/measurement.html",
+ "en/developer/measurement.html",
9,
listOf("formatter", "metric", "number", "locale", "temperature", "conversion", "api"),
listOf("metric-formatter", "number-formatter", "measurement"),
diff --git a/scripts/check-doc-coverage.js b/scripts/check-doc-coverage.js
index 7bebbaad78..3d73aac5ef 100644
--- a/scripts/check-doc-coverage.js
+++ b/scripts/check-doc-coverage.js
@@ -12,7 +12,7 @@ const path = require("path");
const { forEachDocPage } = require("./lib/frontmatter");
const REPO_ROOT = path.resolve(process.argv[2] || ".");
-const DOCS_DIR = path.join(REPO_ROOT, "docs");
+const DOCS_DIR = path.join(REPO_ROOT, "docs", "en");
// Map of feature module directory names to expected doc page slugs.
// Modules not listed here are considered internal (no user-facing docs required).
diff --git a/scripts/check-doc-freshness.js b/scripts/check-doc-freshness.js
index a73c6261a8..58099327bf 100644
--- a/scripts/check-doc-freshness.js
+++ b/scripts/check-doc-freshness.js
@@ -13,7 +13,7 @@ const { parseFrontmatter, forEachDocPage } = require("./lib/frontmatter");
const args = process.argv.slice(2);
const positional = args.filter(a => !a.startsWith("--"));
-const DOCS_DIR = path.resolve(positional[0] || "docs");
+const DOCS_DIR = path.resolve(positional[0] || path.join("docs", "en"));
const maxAgeArg = args.find(a => a.startsWith("--max-age-days="));
const MAX_AGE_DAYS = maxAgeArg ? parseInt(maxAgeArg.split("=")[1], 10) : 180;
diff --git a/scripts/sync-android-docs.js b/scripts/sync-android-docs.js
index b555f87db0..8281a5db6f 100755
--- a/scripts/sync-android-docs.js
+++ b/scripts/sync-android-docs.js
@@ -37,8 +37,8 @@ const IMAGE_EXTENSIONS = new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".web
const ANDROID_REPO_ROOT = positionalArgs.length > 0
? path.resolve(positionalArgs[0])
: path.resolve(__dirname, "..");
-const SRC_DOCS_DIR = path.join(ANDROID_REPO_ROOT, "docs");
-const SRC_SCREENSHOTS_DIR = path.join(SRC_DOCS_DIR, "assets", "screenshots");
+const SRC_DOCS_DIR = path.join(ANDROID_REPO_ROOT, "docs", "en");
+const SRC_SCREENSHOTS_DIR = path.join(ANDROID_REPO_ROOT, "docs", "assets", "screenshots");
if (!fs.existsSync(SRC_DOCS_DIR)) {
console.error(`Error: docs directory not found at ${SRC_DOCS_DIR}`);
diff --git a/scripts/validate-doc-links.js b/scripts/validate-doc-links.js
index dcd82c39f2..1473e23015 100644
--- a/scripts/validate-doc-links.js
+++ b/scripts/validate-doc-links.js
@@ -11,7 +11,9 @@ const fs = require("fs");
const path = require("path");
const { discoverSlugs, forEachDocPage } = require("./lib/frontmatter");
-const DOCS_DIR = path.resolve(process.argv[2] || "docs");
+const DOCS_DIR = path.resolve(process.argv[2] || path.join("docs", "en"));
+// Assets (screenshots) live at docs/ root, not inside the en/ locale folder
+const DOCS_ROOT = path.resolve(DOCS_DIR, "..");
const IMAGE_EXTS = new Set([".png", ".jpg", ".jpeg", ".gif", ".svg", ".webp"]);
// Collect known page slugs from both sections
@@ -64,7 +66,7 @@ forEachDocPage(DOCS_DIR, (filePath, slug, section) => {
if (/^https?:/.test(imgPath)) continue;
const resolved = imgPath.startsWith("/")
- ? path.join(DOCS_DIR, imgPath)
+ ? path.join(DOCS_ROOT, imgPath)
: path.resolve(path.dirname(filePath), imgPath);
if (!fs.existsSync(resolved)) {
console.log(` ERROR: ${section}/${slug}.md:${lineNum} โ missing image '${imgPath}'`);
Served by rngit 1.5.4 - Generated in 0.68s